﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Globalization;

namespace System.Windows.Art
{
    public struct HsvColor : IColor
    {
        private Double scAlpha, scHue, scSaturation, scValue;

        private HsvColor(Double scAlpha, Double scHue, Double scSaturation, Double scValue)
        {
            this.scAlpha = scAlpha;
            this.scHue = scHue;
            this.scSaturation = scSaturation;
            this.scValue = scValue;

            this.ScHue = scHue;
            this.ScSaturation = scSaturation;
            this.ScValue = scValue;
        }

        public static HsvColor FromScHsv(Double scAlpha, Double scHue, Double scSaturation, Double scValue)
        {
            return new HsvColor(scAlpha, scHue, scSaturation, scValue);
        }

        public ColorSpaceBase Space { get { return ColorSpaces.Hsv; } }
        public Color ToColor() { return ColorSpaceConverter.ToColor(this); }

        public Double GetValue(ColorChannel channel)
        {
            var space = ColorSpaces.Hsv;
            if (channel == space.Alpha) return this.scAlpha;
            else if (channel == space.Hue) return this.scHue;
            else if (channel == space.Saturation) return this.scSaturation;
            else if (channel == space.Value) return this.scValue;
            else throw new InvalidOperationException();
        }

        public void SetValue(ColorChannel channel, Double value)
        {
            var space = ColorSpaces.Hsv;
            if (channel == space.Alpha) this.scAlpha = value;
            else if (channel == space.Hue) this.scHue = value;
            else if (channel == space.Saturation) this.scSaturation = value;
            else if (channel == space.Value) this.scValue = value;
            else throw new InvalidOperationException();
        }

        public Double GetClassicValue(ColorChannel channel)
        {
            var space = ColorSpaces.Hsv;
            if (channel == space.Alpha) return this.Alpha;
            else if (channel == space.Hue) return this.Hue;
            else if (channel == space.Saturation) return this.Saturation;
            else if (channel == space.Value) return this.Value;
            else throw new InvalidOperationException();
        }

        public void SetClassicValue(ColorChannel channel, Double value)
        {
            var space = ColorSpaces.Hsv;
            if (channel == space.Alpha) this.Alpha = (Byte)value;
            else if (channel == space.Hue) this.Hue = (Int32)value;
            else if (channel == space.Saturation) this.Saturation = (Int32)value;
            else if (channel == space.Value) this.Value = (Int32)value;
            else throw new InvalidOperationException();
        }

        public Byte Alpha
        {
            get
            {
                if (this.scAlpha < 0) return 0;
                else if (this.scAlpha > 1) return 255;
                else return (Byte)Math.Round(255 * this.scAlpha);
            }
            set { this.scAlpha = value / 255.0; }
        }

        /// <summary>
        /// Classic Alpha scaled to [0; 1].
        /// <remarks>Linear, unbounded.</remarks>
        /// </summary>
        public Double ScAlpha
        {
            get { return this.scAlpha; }
            set { this.scAlpha = value; }
        }

        /// <summary>
        /// Must fall into the range [0; 360].
        /// <remarks>Cyclic.</remarks>
        /// </summary>
        public Int32 Hue
        {
            get { return (Int32)Math.Round(360 * this.scHue); }
            set
            {
                value %= 360;
                if (value < 0) value += 360;
                this.scHue = value / 360.0;
            }
        }

        /// <summary>
        /// Classic Hue scaled to [0; 1].
        /// <remarks>Linear, cyclic.</remarks>
        /// </summary>
        public Double ScHue
        {
            get { return this.scHue; }
            set
            {
                value %= 1;
                if (value < 0) value++;
                this.scHue = value;
            }
        }

        /// <summary>
        /// Must fall into the range [0; 100].
        /// </summary>
        public Int32 Saturation
        {
            get { return (Int32)Math.Round(100 * this.scSaturation); }
            set
            {
                if (value < 0 || value > 100) throw new ArgumentOutOfRangeException("value");
                this.scSaturation = value / 100.0;
            }
        }

        /// <summary>
        /// Classic Saturation scaled to [0; 1].
        /// <remarks>Linear, bounded [0; 1].</remarks>
        /// </summary>
        public Double ScSaturation
        {
            get { return this.scSaturation; }
            set
            {
                if (value < 0 || value > 1) throw new ArgumentOutOfRangeException("value");
                else this.scSaturation = value;
            }
        }

        /// <summary>
        /// Must fall into the range [0; 100].
        /// </summary>
        public Int32 Value
        {
            get { return (this.scValue > 1) ? 100 : (Int32)Math.Round(100 * ColorSpaceConverter.InverseScRgbCurve(this.scValue)); }
            set
            {
                if (value < 0 || value > 100) throw new ArgumentOutOfRangeException("value");
                this.scValue = ColorSpaceConverter.ScRgbCurve(value / 100.0);
            }
        }

        /// <summary>
        /// Classic Lightness scaled to [0; 1].
        /// <remarks>Non-linear, unbounded (top).</remarks>
        /// </summary>
        public Double ScValue
        {
            get { return this.scValue; }
            set
            {
                if (value < 0) throw new ArgumentOutOfRangeException("value");
                else this.scValue = value;
            }
        }

        public override String ToString()
        {
            return this.ToString("g", CultureInfo.CurrentCulture);
        }

        public String ToString(String format)
        {
            return this.ToString(format, CultureInfo.CurrentCulture);
        }

        public String ToString(String format, IFormatProvider provider)
        {
            var builder = new StringBuilder();

            builder.Append(this.Space.ToString()).Append('#');
            builder.Append(this.scAlpha.ToString(format, provider)).Append("; ");
            builder.Append(this.scHue.ToString(format, provider)).Append("; ");
            builder.Append(this.scSaturation.ToString(format, provider)).Append("; ");
            builder.Append(this.scValue.ToString(format, provider));

            return builder.ToString();
        }

        public Object Clone()
        {
            return this.MemberwiseClone();
        }
    }
}
